/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "../game.h"


#define PM_OVERCLIP					1.001f

#define PM_STEP_SIZE				18.0f

#define PM_MIN_WALK_NORMAL			0.7f

#define PM_MAX_CLIP_PLANES			5

// Movement parameters
#define PM_STOP_SPEED				100.0f

#define PM_PRONE_SCALE				0.20f
#define PM_DUCK_SCALE				0.25f
#define PM_SWIM_SCALE				0.50f

#define PM_ACCELERATE				10.0f
#define PM_AIR_ACCELERATE			1.0f
#define PM_FLY_ACCELERATE			8.0f
#define PM_WATER_ACCELERATE			4.0f

#define PM_FRICTION					6.0f
#define PM_AIR_FRICTION				0.0f
#define PM_FLY_FRICTION				3.0f
#define PM_WATER_FRICTION			1.0f
#define PM_LADDER_FRICTION			14.0f
#define PM_NOCLIP_FRICTION			12.0f


/*
 ==================
 odPhysicsPlayer::AddTouchEntity
 ==================
*/
void odPhysicsPlayer::AddTouchEntity (int entityNum){

	int		i;

	if (entityNum == ENTITYNUM_WORLD)
		return;

	if (numTouchEntities == MAX_TOUCH_ENTITIES)
		return;

	// See if it is already added
	for (i = 0; i < numTouchEntities; i++){
		if (touchEntities[i] == entityNum)
			return;
	}

	touchEntities[numTouchEntities++] = entityNum;
}

/*
 ==================
 odPhysicsPlayer::ClipVelocity

 Slide off of the impacting surface
 ==================
*/
void odPhysicsPlayer::ClipVelocity (const odVec3 &in, const odVec3 &normal, odVec3 &out, float overBounce){

	float	backOff;

	backOff = in.Dot(normal);

	if (backOff < 0.0f)
		backOff *= overBounce;
	else
		backOff /= overBounce;

	out = in - (normal * backOff);
}

/*
 ==================
 odPhysicsPlayer::Friction

 Handles both ground friction and water friction
 ==================
*/
void odPhysicsPlayer::Friction (void){

	odVec3	vec;
	float	speed, newSpeed;
	float	control, drop;

	vec = state->velocity;

	if (onGround)
		vec.z = 0.0f;		// Ignore slope movement

	speed = vec.Length();
	if (speed < 1.0f){
		// Allow sinking underwater
		state->velocity.x = 0.0f;
		state->velocity.y = 0.0f;

		return;
	}

	drop = 0.0f;

	// Apply friction
	if (state->pmType == PM_NOCLIP)
		drop += speed * PM_NOCLIP_FRICTION * frameTime;
	else if (state->pmType == PM_SPECTATOR)
		drop += speed * PM_FLY_FRICTION * frameTime;
	else if (onLadder)
		drop += speed * PM_LADDER_FRICTION * frameTime;
	else if (waterLevel > WATERLEVEL_FEET)
		drop += speed * PM_WATER_FRICTION * waterLevel * frameTime;
	else if (onGround){
		// If walking on a slick surface, no friction
		if (!(groundTrace.c.surfaceFlags & SURFACE_SLICK)){
			// If getting knocked back, no friction
			if (!(state->pmFlags & PMF_TIME_KNOCKBACK)){
				control = speed;
				if (control < PM_STOP_SPEED)
					control = PM_STOP_SPEED;

				drop += control * PM_FRICTION * frameTime;
			}
		}
	}
	else
		drop += speed * PM_AIR_FRICTION * frameTime;

	// Scale the velocity
	newSpeed = speed - drop;
	if (newSpeed < 0.0f)
		newSpeed = 0.0f;

	state->velocity *= (newSpeed / speed);
}

/*
 ==================
 odPhysicsPlayer::Accelerate

 Handles user intended acceleration
 ==================
*/
void odPhysicsPlayer::Accelerate (const odVec3 &wishDir, float wishSpeed, float accel){

	float	addSpeed, accelSpeed;

	addSpeed = wishSpeed - state->velocity.Dot(wishDir);
	if (addSpeed <= 0.0f)
		return;

	accelSpeed = accel * frameTime * wishSpeed;
	if (accelSpeed > addSpeed)
		accelSpeed = addSpeed;

	state->velocity += wishDir * accelSpeed;
}

/*
 ==================
 odPhysicsPlayer::UserCmdScale

 Returns the scale factor to apply to movements.
 This allows the clients to use axial -127 to 127 values for all directions
 without getting a sqrt(2) distortion in speed.
 ==================
*/
float odPhysicsPlayer::UserCmdScale (void){

	float	total, speed;
	int		forwardMove, rightMove, upMove;
	int		max;

	forwardMove = M_Abs(userCmd.forwardMove);
	rightMove = M_Abs(userCmd.rightMove);
	upMove = M_Abs(userCmd.upMove);

	max = forwardMove;
	if (rightMove > max)
		max = rightMove;
	if (upMove > max)
		max = upMove;

	if (!max)
		return 0.0f;

	forwardMove = userCmd.forwardMove * userCmd.forwardMove;
	rightMove = userCmd.rightMove * userCmd.rightMove;
	upMove = userCmd.upMove * userCmd.upMove;

	total = M_Sqrt((float)(forwardMove + rightMove + upMove));

	speed = state->speed * max / (127.0f * total);

	if (userCmd.buttons & BUTTON_SPRINT)
		speed *= state->sprintSpeedScale;

	return speed;
}

/*
 ==================
 odPhysicsPlayer::CorrectAllInside
 ==================
*/
bool odPhysicsPlayer::CorrectAllInside (trace_t *trace){

	odVec3	point;
	int		x, y, z;

	// Jitter around
	for (x = -1; x <= 1; x++){
		for (y = -1; y <= 1; y++){
			for (z = -1; z <= 1; z++){
				point = state->origin;

				point.x += (float)x;
				point.y += (float)y;
				point.z += (float)z;

				*trace = Trace(point, point, bounds, contentMask, state->clientNum);

				if (!trace->allInside){
					point = state->origin;
					point.z -= 0.25f;

					*trace = Trace(state->origin, point, bounds, contentMask, state->clientNum);

					groundTrace = *trace;

					return true;
				}
			}
		}
	}

	onGround = false;

	state->groundEntity = ENTITYNUM_NONE;

	return false;
}

/*
 ==================
 odPhysicsPlayer::CheckGround
 ==================
*/
void odPhysicsPlayer::CheckGround (void){

	trace_t	trace;
	odVec3	point;

	point = state->origin;
	point.z -= 0.25f;

	trace = Trace(state->origin, point, bounds, contentMask, state->clientNum);

	groundTrace = trace;

	// Do something corrective if the trace is inside a volume
	if (trace.allInside){
		if (!CorrectAllInside(&trace))
			return;
	}

	// If the trace didn't hit anything, we are in free fall
	if (trace.fraction == 1.0f){
		onGround = false;

		state->groundEntity = ENTITYNUM_NONE;

		return;
	}

	// Check if getting thrown off the ground
	if (state->velocity.z > 0.0f && trace.c.plane.normal.Dot(state->velocity) > 10.0f){
		onGround = false;

		state->groundEntity = ENTITYNUM_NONE;

		return;
	}

	// Slopes that are too steep will not be considered on ground
	if (trace.c.plane.normal.z < PM_MIN_WALK_NORMAL){
		onGround = false;

		state->groundEntity = ENTITYNUM_NONE;

		return;
	}

	onGround = true;

	// Hitting solid ground will end a water jump
	if (state->pmFlags & PMF_TIME_WATERJUMP){
		state->pmFlags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND);
		state->pmTime = 0;
	}

	if (state->groundEntity == ENTITYNUM_NONE){
		// Just hit the ground.
		// Don't do landing time if we were just going down a slope.
		if (previousVelocity.z < -200.0f){
			// Don't allow another jump for a little while
			state->pmFlags |= PMF_TIME_LAND;
			state->pmTime = 250;
		}
	}

	state->groundEntity = trace.c.entityNum;

	AddTouchEntity(trace.c.entityNum);
}

/*
 ==================
 odPhysicsPlayer::CheckLadder
 ==================
*/
void odPhysicsPlayer::CheckLadder (void){

	// TODO!!!

	onLadder = false;
}

/*
 ==================
 odPhysicsPlayer::CheckDuck
 ==================
*/
void odPhysicsPlayer::CheckDuck (void){

	// TODO!!!

//	bounds.Set(-10.0f, 10.0f);
	bounds.Set(-16.0f, -16.0f, 0.0f, 16.0f, 16.0f, 80.0f);
}

/*
 ==================
 odPhysicsPlayer::CheckProne
 ==================
*/
bool odPhysicsPlayer::CheckProne (void){

	// TODO!!!

	return false;
}

/*
 ==================
 odPhysicsPlayer::CheckJump
 ==================
*/
bool odPhysicsPlayer::CheckJump (void){

	if (state->pmFlags & PMF_RESPAWNED)
		return false;		// Don't allow jump until all buttons are up

	if (userCmd.upMove < 10)
		return false;		// Not holding jump

	// Must wait for jump to be released
	if (state->pmFlags & PMF_JUMP_HELD){
		// Clear upMove so UserCmdScale doesn't lower running speed
		userCmd.upMove = 0;
		return false;
	}

	// Jumping away
	onGround = false;

	state->groundEntity = ENTITYNUM_NONE;

	state->pmFlags |= PMF_JUMP_HELD;
	state->velocity.z = 270.0f;

	return true;
}

/*
 ==================
 odPhysicsPlayer::CheckWaterJump
 ==================
*/
bool odPhysicsPlayer::CheckWaterJump (void){

	odVec3	spot, flatForward;
	int		contents;

	if (state->pmTime)
		return false;

	// Check for water jump
	if (waterLevel != WATERLEVEL_WAIST)
		return false;

	flatForward.Set(viewForward.x, viewForward.y, 0.0f);
	flatForward.Normalize();

	spot = state->origin + flatForward * 30.0f;
	spot.z += 4.0f;

	contents = Contents(spot, bounds_zero, MASK_ALL, state->clientNum);
	if (!(contents & CONTENT_SOLID))
		return false;

	spot.z += 16.0f;

	contents = Contents(spot, bounds_zero, MASK_ALL, state->clientNum);
	if (contents)
		return false;

	// Jump out of water
	onGround = false;

	state->groundEntity = ENTITYNUM_NONE;

	state->pmFlags |= PMF_TIME_WATERJUMP;
	state->pmTime = 2000;

	state->velocity = viewForward * 200.0f;
	state->velocity.z = 350.0f;

	return true;
}

/*
 ==================
 odPhysicsPlayer::SlideMove

 Returns true if the velocity was clipped in some way
 ==================
*/
bool odPhysicsPlayer::SlideMove (bool gravity){

	trace_t	trace;
	odVec3	planes[PM_MAX_CLIP_PLANES];
	int		numPlanes = 0;
	odVec3	primalVelocity, clipVelocity;
	odVec3	endVelocity, endClipVelocity;
	odVec3	dir, end;
	float	timeLeft;
	float	into;
	int		bumpCount;
	int		i, j, k;

	primalVelocity = state->velocity;

	if (gravity){
		endVelocity = state->velocity;
		endVelocity.z -= state->gravity * frameTime;

		state->velocity.z = (state->velocity.z + endVelocity.z) * 0.5f;

		primalVelocity.z = endVelocity.z;

		// Slide along the ground plane
		if (onGround)
			ClipVelocity(state->velocity, groundTrace.c.plane.normal, state->velocity, PM_OVERCLIP);
	}

	timeLeft = frameTime;

	// Never turn against the ground plane
	if (onGround)
		planes[numPlanes++] = groundTrace.c.plane.normal;

	// Never turn against original velocity
	planes[numPlanes] = state->velocity;
	planes[numPlanes].Normalize();

	numPlanes++;

	for (bumpCount = 0; bumpCount < 4; bumpCount++){
		// Calculate position we are trying to move to
		end = state->origin + state->velocity * timeLeft;

		// See if we can make it there
		trace = Trace(state->origin, end, bounds, contentMask, state->clientNum);

		if (trace.allInside){
			// Entity is completely trapped in another volume.
			// Don't build up falling damage, but allow sideways acceleration.
			state->velocity.z = 0.0f;
			return true;
		}

		if (trace.fraction > 0.0f){
			// Actually covered some distance
			state->origin = trace.endPos;

			if (trace.fraction == 1.0f)
				break;		// Moved the entire distance
		}

		// Save entity for contact
		AddTouchEntity(trace.c.entityNum);

		timeLeft -= timeLeft * trace.fraction;

		if (numPlanes >= PM_MAX_CLIP_PLANES){
			// This shouldn't really happen
			state->velocity.Clear();
			return true;
		}

		// If this is the same plane we hit before, nudge velocity out along
		// it, which fixes some epsilon issues with non-axial planes
		for (i = 0; i < numPlanes; i++){
			if (planes[i].Dot(trace.c.plane.normal) > 0.999f){
				state->velocity += trace.c.plane.normal;
				break;
			}
		}

		if (i < numPlanes)
			continue;

		planes[numPlanes++] = trace.c.plane.normal;

		// Modify velocity so it parallels all of the clip planes.
		// Find a plane that it enters.
		for (i = 0; i < numPlanes; i++){
			into = planes[i].Dot(state->velocity);
			if (into >= 0.1f)
				continue;		// Move doesn't interact with the plane

			// See how hard we are hitting things
			if (-into > impactSpeed)
				impactSpeed = -into;

			// Slide along the plane
			ClipVelocity(state->velocity, planes[i], clipVelocity, PM_OVERCLIP);
			ClipVelocity(endVelocity, planes[i], endClipVelocity, PM_OVERCLIP);

			// See if there is a second plane that the new move enters
			for (j = 0; j < numPlanes; j++){
				if (j == i)
					continue;

				if (planes[j].Dot(clipVelocity) >= 0.1f)
					continue;		// Move doesn't interact with the plane

				// Try clipping the move to the plane
				ClipVelocity(clipVelocity, planes[j], clipVelocity, PM_OVERCLIP);
				ClipVelocity(endClipVelocity, planes[j], endClipVelocity, PM_OVERCLIP);

				// See if it goes back into the first clip plane
				if (planes[i].Dot(clipVelocity) >= 0.0f)
					continue;

				// Slide the original velocity along the crease
				dir.Cross(planes[i], planes[j]);
				dir.Normalize();

				clipVelocity = dir * dir.Dot(state->velocity);
				endClipVelocity = dir * dir.Dot(endVelocity);

				// See if there is a third plane that the new move enters
				for (k = 0; k < numPlanes; k++){
					if (k == i || k == j)
						continue;

					if (planes[k].Dot(clipVelocity) >= 0.1f)
						continue;		// Move doesn't interact with the plane

					// Stop dead at a tripple plane interaction
					state->velocity.Clear();
					return true;
				}
			}

			// If we have fixed all interactions, try another move
			state->velocity = clipVelocity;
			endVelocity = endClipVelocity;

			break;
		}
	}

	if (gravity)
		state->velocity = endVelocity;

	// Don't change velocity if in a timer
	if (state->pmTime)
		state->velocity = primalVelocity;

	return (bumpCount != 0);
}

/*
 ==================
 odPhysicsPlayer::StepSlideMove
 ==================
*/
void odPhysicsPlayer::StepSlideMove (bool gravity){

	trace_t	trace;
	odVec3	startOrigin, startVelocity;
	odVec3	downOrigin, downVelocity;
	odVec3	up, down;
	float	stepSize;

	startOrigin = state->origin;
	startVelocity = state->velocity;

	if (!SlideMove(gravity))
		return;		// We got exactly where we wanted to go first try

	down = startOrigin;
	down.z -= PM_STEP_SIZE;

	trace = Trace(startOrigin, down, bounds, contentMask, state->clientNum);

	// Never step up when you still have up velocity
	up.Set(0.0f, 0.0f, 1.0f);

	if (state->velocity.z > 0.0f && (trace.fraction == 1.0f || up.Dot(trace.c.plane.normal) < PM_MIN_WALK_NORMAL))
		return;

	downOrigin = state->origin;
	downVelocity = state->velocity;

	up = startOrigin;
	up.z += PM_STEP_SIZE;

	// Test the player position if they were a step height higher
	trace = Trace(startOrigin, up, bounds, contentMask, state->clientNum);

	if (trace.allInside)
		return;		// Can't step up

	stepSize = trace.endPos.z - startOrigin.z;

	// Try slide move from this position
	state->origin = trace.endPos;
	state->velocity = startVelocity;

	SlideMove(gravity);

	// Push down the final amount
	down = state->origin;
	down.z -= stepSize;

	trace = Trace(state->origin, down, bounds, contentMask, state->clientNum);

	if (!trace.allInside)
		state->origin = trace.endPos;

	if (trace.fraction != 1.0f)
		ClipVelocity(state->velocity, trace.c.plane.normal, state->velocity, PM_OVERCLIP);
}

/*
 ==================
 odPhysicsPlayer::DeadMove
 ==================
*/
void odPhysicsPlayer::DeadMove (void){

	float	forward;

	if (!onGround)
		return;

	// Extra friction
	forward = state->velocity.Length() - 20.0f;

	if (forward <= 0.0f)
		state->velocity.Clear();
	else {
		state->velocity.Normalize();
		state->velocity *= forward;
	}
}

/*
 ==================
 odPhysicsPlayer::WalkMove
 ==================
*/
void odPhysicsPlayer::WalkMove (void){

	odVec3	wishDir;
	float	wishSpeed;
	float	scale, vel;

	if (CheckJump()){
		AirMove();
		return;
	}

	Friction();

	scale = UserCmdScale();

	// Project moves down the flat plane
	viewForward.z = 0.0f;
	viewRight.z = 0.0f;

	// Project the forward and right view directions onto the ground plane
	ClipVelocity(viewForward, groundTrace.c.plane.normal, viewForward, PM_OVERCLIP);
	ClipVelocity(viewRight, groundTrace.c.plane.normal, viewRight, PM_OVERCLIP);

	viewForward.Normalize();
	viewRight.Normalize();

	wishDir = viewForward * userCmd.forwardMove + viewRight * userCmd.rightMove;

	wishSpeed = wishDir.Normalize() * scale;

	// Clamp the speed lower if prone or ducking
	if (state->pmFlags & PMF_PRONE){
		if (wishSpeed > state->speed * PM_PRONE_SCALE)
			wishSpeed = state->speed * PM_PRONE_SCALE;
	}
	else if (state->pmFlags & PMF_DUCKED){
		if (wishSpeed > state->speed * PM_DUCK_SCALE)
			wishSpeed = state->speed * PM_DUCK_SCALE;
	}

	// Clamp the speed lower if wading or walking on the bottom
	if (waterLevel != WATERLEVEL_NONE){
		scale = 1.0f - (1.0f - PM_SWIM_SCALE) * (waterLevel / 3.0f);

		// Half the speed in lava
		if (waterType & CONTENT_LAVA)
			scale *= 0.5f;

		if (wishSpeed > state->speed * scale)
			wishSpeed = state->speed * scale;
	}

	// When a player gets hit, they temporarily lose full control, which allows
	// them to be moved a bit
	if ((state->pmFlags & PMF_TIME_KNOCKBACK) || (groundTrace.c.surfaceFlags & SURFACE_SLICK)){
		Accelerate(wishDir, wishSpeed, PM_AIR_ACCELERATE);

		state->velocity.z -= state->gravity * frameTime;
	}
	else
		Accelerate(wishDir, wishSpeed, PM_ACCELERATE);

	vel = state->velocity.Length();

	// Slide along the ground plane
	ClipVelocity(state->velocity, groundTrace.c.plane.normal, state->velocity, PM_OVERCLIP);

	// Don't decrease velocity when going up or down a slope
	state->velocity.Normalize();
	state->velocity *= vel;

	// Don't do anything if standing still
	if (!state->velocity.x && !state->velocity.y)
		return;

	StepSlideMove(false);
}

/*
 ==================
 odPhysicsPlayer::AirMove
 ==================
*/
void odPhysicsPlayer::AirMove (void){

	odVec3	wishDir;
	float	wishSpeed;
	float	scale;

	Friction();

	scale = UserCmdScale();

	// Project moves down the flat plane
	viewForward.z = 0.0f;
	viewRight.z = 0.0f;

	viewForward.Normalize();
	viewRight.Normalize();

	wishDir = viewForward * userCmd.forwardMove + viewRight * userCmd.rightMove;
	wishDir.z = 0.0f;

	wishSpeed = wishDir.Normalize() * scale;

	// Not on ground, so little effect on velocity
	Accelerate(wishDir, wishSpeed, PM_AIR_ACCELERATE);

	// We may have a ground plane that is very steep, even though we don't have
	// a ground entity.
	// Slide along the steep plane.
	if (onGround)
		ClipVelocity(state->velocity, groundTrace.c.plane.normal, state->velocity, PM_OVERCLIP);

	StepSlideMove(true);
}

/*
 ==================
 odPhysicsPlayer::FlyMove
 ==================
*/
void odPhysicsPlayer::FlyMove (void){

	odVec3	wishDir;
	float	wishSpeed;
	float	scale;

	// Normal slowdown
	Friction();

	scale = UserCmdScale();

	// User intentions
	if (!scale)
		wishDir.Clear();
	else {
		wishDir = viewForward * scale * userCmd.forwardMove + viewRight * scale * userCmd.rightMove;
		wishDir.z += scale * userCmd.upMove;
	}

	wishSpeed = wishDir.Normalize();

	Accelerate(wishDir, wishSpeed, PM_FLY_ACCELERATE);

	StepSlideMove(false);
}

/*
 ==================
 odPhysicsPlayer::WaterMove
 ==================
*/
void odPhysicsPlayer::WaterMove (void){

	odVec3	wishDir;
	float	wishSpeed;
	float	scale, vel;

	if (CheckWaterJump()){
		WaterJumpMove();
		return;
	}

	Friction();

	scale = UserCmdScale();

	// User intentions
	if (!scale)
		wishDir.Set(0.0f, 0.0f, -60.0f);
	else {
		wishDir = viewForward * scale * userCmd.forwardMove + viewRight * scale * userCmd.rightMove;
		wishDir.z += scale * userCmd.upMove;
	}

	// Scale speed according to water level
	scale = 1.0f - (1.0f - PM_SWIM_SCALE) * (waterLevel / (float)WATERLEVEL_HEAD);

	// Half the speed in lava
	if (waterType & CONTENT_LAVA)
		scale *= 0.5f;

	wishSpeed = wishDir.Normalize();

	if (wishSpeed > state->speed * scale)
		wishSpeed = state->speed * scale;

	if (waterType & CONTENT_LAVA)
		Accelerate(wishDir, wishSpeed, PM_WATER_ACCELERATE * 0.5f);
	else
		Accelerate(wishDir, wishSpeed, PM_WATER_ACCELERATE);

	// Make sure we can go up slopes easily under water
	if (onGround && groundTrace.c.plane.normal.Dot(state->velocity) < 0.0f){
		vel = state->velocity.Length();

		// Slide along the ground plane
		ClipVelocity(state->velocity, groundTrace.c.plane.normal, state->velocity, PM_OVERCLIP);

		state->velocity.Normalize();
		state->velocity *= vel;
	}

	SlideMove(false);
}

/*
 ==================
 odPhysicsPlayer::WaterJumpMove
 ==================
*/
void odPhysicsPlayer::WaterJumpMove (void){

	// Water jump has no control, but falls
	StepSlideMove(true);

	state->velocity.z -= state->gravity * frameTime;
	if (state->velocity.z < 0.0f){
		// Cancel as soon as we are falling down again
		state->pmFlags &= ~PMF_ALL_TIMES;
		state->pmTime = 0;
	}
}

/*
 ==================
 odPhysicsPlayer::LadderMove
 ==================
*/
void odPhysicsPlayer::LadderMove (void){

	// TODO!!!
}

/*
 ==================
 odPhysicsPlayer::NoclipMove
 ==================
*/
void odPhysicsPlayer::NoclipMove (void){

	odVec3	wishDir;
	float	wishSpeed;
	float	speed, newSpeed;
	float	scale, drop, control;

	// Friction
	speed = state->velocity.Length();

	if (speed < 1.0f)
		state->velocity.Clear();
	else {
		drop = 0.0f;

		control = speed;
		if (control < PM_STOP_SPEED)
			control = PM_STOP_SPEED;

		drop += control * PM_NOCLIP_FRICTION * frameTime;

		// Scale the velocity
		newSpeed = speed - drop;
		if (newSpeed < 0.0f)
			newSpeed = 0.0f;

		state->velocity *= (newSpeed / speed);
	}

	// Accelerate
	scale = UserCmdScale();

	wishDir = viewForward * userCmd.forwardMove + viewRight * userCmd.rightMove;
	wishDir.z += userCmd.upMove;

	wishSpeed = wishDir.Normalize() * scale;

	Accelerate(wishDir, wishSpeed, PM_ACCELERATE);

	// Move
	state->origin += state->velocity * frameTime;
}

/*
 ==================
 odPhysicsPlayer::SetWaterLevel
 ==================
*/
void odPhysicsPlayer::SetWaterLevel (void){

	odVec3	point;
	int		contents;

	point = state->origin;

	waterLevel = WATERLEVEL_NONE;
	waterType = 0;

	point.z = state->origin.z + bounds[0][2] + 1.0f;

	contents = Contents(point, bounds_zero, MASK_LIQUID, -1);
	if (contents & MASK_LIQUID){
		waterLevel = WATERLEVEL_FEET;
		waterType = contents;

		point.z = state->origin.z + (bounds[1][2] - bounds[0][2]) * 0.5f;

		contents = Contents(point, bounds_zero, MASK_LIQUID, -1);
		if (contents & MASK_LIQUID){
			waterLevel = WATERLEVEL_WAIST;

			point.z = state->origin.z + bounds[1][2] - 1.0f;

			contents = Contents(point, bounds_zero, MASK_LIQUID, -1);
			if (contents & MASK_LIQUID)
				waterLevel = WATERLEVEL_HEAD;
		}
	}
}

/*
 ==================
 odPhysicsPlayer::DropTimers
 ==================
*/
void odPhysicsPlayer::DropTimers (void){

	if (!state->pmTime)
		return;

	if (frameMsec >= state->pmTime){
		state->pmFlags &= ~PMF_ALL_TIMES;
		state->pmTime = 0;
	}
	else
		state->pmTime -= frameMsec;
}

/*
 ==================
 odPhysicsPlayer::Move
 ==================
*/
void odPhysicsPlayer::Move (playerState_t *playerState, int msec){

	if (!Contents || !Trace)
		odSystem->Error(false, "odPhysicsPlayer::Move: Contents == NULL || Trace == NULL");

	state = playerState;

	// Clear results
	bounds.Zero();

	numTouchEntities = 0;

	waterLevel = WATERLEVEL_NONE;
	waterType = 0;

	// Clear the respawned flag if attack is cleared
	if (state->pmType != PM_DEAD && !(userCmd.buttons & BUTTON_ATTACK))
		state->pmFlags &= ~PMF_RESPAWNED;

	// Clear the jump held flag if needed
	if (userCmd.upMove < 10)
		state->pmFlags &= ~PMF_JUMP_HELD;

	// If frozen, no movement at all
	if (state->pmType == PM_FREEZE)
		return;

	// If talking, disallow all other input
	if (userCmd.flags & UCF_TALKING){
		userCmd.forwardMove = 0;
		userCmd.rightMove = 0;
		userCmd.upMove = 0;
	}

	// Determine the time
	frameTime = MS2SEC(msec);
	frameMsec = msec;

	// Clear all local variables
	onGround = false;
	onLadder = false;

	impactSpeed = 0.0f;

	// Save old origin and velocity
	previousOrigin = state->origin;
	previousVelocity = state->velocity;

	// Update view angles
	UpdateViewAngles(state);

	state->angles.ToVectors(&viewForward, &viewRight, NULL);

	// Special case for noclip move
	if (state->pmType == PM_NOCLIP){
		NoclipMove();
		DropTimers();

		return;
	}

	// Special case for spectator move
	if (state->pmType == PM_SPECTATOR){
		CheckDuck();
		FlyMove();
		DropTimers();

		return;
	}

	// No control when dead
	if (state->pmType == PM_DEAD){
		userCmd.forwardMove = 0;
		userCmd.rightMove = 0;
		userCmd.upMove = 0;
	}

	// Set bounds
	if (!CheckProne())
		CheckDuck();

	// Set ground entity
	CheckGround();

	// Check for ladder
	CheckLadder();

	// Set water level and type
	SetWaterLevel();

	// Drop timers
	DropTimers();

	// Move
	if (state->pmType == PM_DEAD)
		DeadMove();

	if (onLadder)
		LadderMove();
	else if (state->pmFlags & PMF_TIME_WATERJUMP)
		WaterJumpMove();
	else if (waterLevel > WATERLEVEL_FEET)
		WaterMove();
	else if (onGround)
		WalkMove();
	else
		AirMove();

	// Set ground entity
	CheckGround();

	// Set water level and type
	SetWaterLevel();

	if (state->pmFlags & PMF_JUMP_HELD)
		userCmd.upMove = 20;
}


/*
 ==============================================================================

 PUBLIC INTERFACE

 ==============================================================================
*/


/*
 ==================
 odPhysicsPlayer::odPhysicsPlayer
 ==================
*/
odPhysicsPlayer::odPhysicsPlayer (void){

	frameMsec = 0;
	frameTime = 0.0f;

	viewForward.Clear();
	viewRight.Clear();

	onGround = false;
	onLadder = false;

	memset(&groundTrace, 0, sizeof(trace_t));

	ladderForward = false;
	ladderNormal.Clear();

	impactSpeed = 0.0f;

	previousOrigin.Clear();
	previousVelocity.Clear();

	Contents = NULL;
	Trace = NULL;

	state = NULL;

	contentMask = MASK_ALL;

	memset(&userCmd, 0, sizeof(userCmd_t));

	bounds.Zero();

	numTouchEntities = 0;
	memset(&touchEntities, 0, MAX_TOUCH_ENTITIES * sizeof(int));

	waterLevel = WATERLEVEL_NONE;
	waterType = 0;
}

/*
 ==================
 odPhysicsPlayer::SetContentsFunction
 ==================
*/
void odPhysicsPlayer::SetContentsFunction (int (*contents)(const odVec3 &, const odBounds &, int, int)){

	Contents = contents;
}

/*
 ==================
 odPhysicsPlayer::SetTraceFunction
 ==================
*/
void odPhysicsPlayer::SetTraceFunction (trace_t (*trace)(const odVec3 &, const odVec3 &, const odBounds &, int, int)){

	Trace = trace;
}

/*
 ==================
 odPhysicsPlayer::SetMoveMsec
 ==================
*/
void odPhysicsPlayer::SetMoveMsec (int moveMsec){

	if (moveMsec < 1)
		moveMsec = 1;

	this->moveMsec = moveMsec;
}

/*
 ==================
 odPhysicsPlayer::SetContentMask
 ==================
*/
void odPhysicsPlayer::SetContentMask (int contentMask){

	this->contentMask = contentMask;
}

/*
 ==================
 odPhysicsPlayer::SetUserCmd
 ==================
*/
void odPhysicsPlayer::SetUserCmd (const userCmd_t *userCmd){

	this->userCmd = *userCmd;
}

/*
 ==================
 odPhysicsPlayer::GetBounds
 ==================
*/
const odBounds &odPhysicsPlayer::GetBounds (void) const {

	return bounds;
}

/*
 ==================
 odPhysicsPlayer::GetNumTouchEntities
 ==================
*/
int odPhysicsPlayer::GetNumTouchEntities (void) const {

	return numTouchEntities;
}

/*
 ==================
 odPhysicsPlayer::GetTouchEntity
 ==================
*/
int odPhysicsPlayer::GetTouchEntity (int index) const {

	if (index < 0 || index >= numTouchEntities)
		return ENTITYNUM_NONE;

	return touchEntities[index];
}

/*
 ==================
 odPhysicsPlayer::GetWaterLevel
 ==================
*/
waterLevel_t odPhysicsPlayer::GetWaterLevel (void) const {

	return waterLevel;
}

/*
 ==================
 odPhysicsPlayer:GetWaterType
 ==================
*/
int odPhysicsPlayer::GetWaterType (void) const {

	return waterType;
}

/*
 ==================
 odPhysicsPlayer::UpdateViewAngles

 This can be used instead of MovePlayer when only the view angles are being
 updated instead of a full move
 ==================
*/
void odPhysicsPlayer::UpdateViewAngles (playerState_t *playerState){

	short	tmp;
	int		i;

	// TODO: let the player look around if dead but still on the battlefield?
	if (playerState->pmType == PM_DEAD)
		return;		// No view changes at all

	// Circularly clamp the angles with deltas
	for (i = 0; i < 3; i++){
		tmp = userCmd.angles[i] + playerState->deltaAngles[i];

		if (i == PITCH){
			// Don't let the player look up or down more than 90 degrees
			if (tmp < -16000){
				playerState->deltaAngles[i] = -16000 - userCmd.angles[i];
				tmp = -16000;
			}
			else if (tmp > 16000){
				playerState->deltaAngles[i] = 16000 - userCmd.angles[i];
				tmp = 16000;
			}
		}

		playerState->angles[i] = SHORT2ANGLE(tmp);
	}
}

/*
 ==================
 odPhysicsPlayer::MovePlayer

 Can be called by either the server or the client
 ==================
*/
void odPhysicsPlayer::MovePlayer (playerState_t *playerState){

	int		msec, finalTime;

	finalTime = userCmd.time;

	if (finalTime < playerState->userCmdTime)
		return;		// Should not happen

	if (finalTime > playerState->userCmdTime + 1000)
		playerState->userCmdTime = finalTime - 1000;

	// Chop the move up if it is too long, to prevent framerate dependent
	// behaviour
	while (playerState->userCmdTime != finalTime){
		msec = M_ClampLong(finalTime - playerState->userCmdTime, 1, moveMsec);

		Move(playerState, msec);

		playerState->userCmdTime += msec;
	}
}
